import { _decorator, Node, RigidBody, Vec3, ICollisionEvent, ERigidBodyType } from 'cc';
const { ccclass, property } = _decorator;

import { BilliardGame } from './BilliardGame';
import { Vector3 } from '../framework/Vector3';
import { BaseBallSphere } from './BaseBallSphere';
import { BallAttribute } from '../core/BallAttribute';
import { EventManager } from '../core/EventManager';
import { GeometryTool  } from '../framework/GeometryTool';
import { SendWebSocket } from '../websocket/SendWebSocket';
import { TableAtrribute } from '../core/TableAttribute';
import { WebFrameController } from '../user/WebFrameController';
import { SoundManager } from '../framework/SoundManager';
@ccclass('MainBallSphere')
export class MainBallSphere extends BaseBallSphere {
  private maskBallNode = null;
  private prePosition = new Vec3();
  private hitBallPosition = new Vec3();
  private virtualBallPosition = new Vec3();
  private whiteLineVelocity = new Vec3();
  // 击球时初时速度和位置
  private initHitLineVelocity = new Vec3();
  private initWhiteBallPosition = new Vec3();
  private initSpinVelocity = new Vec3();
  // 是否需要修正
  private isNeedCorrect = false;
  private spinOffsetPos = new Vec3();
  onLoad() {
    super.onLoad();
    // 白球刚体
    this.ballCollider.on('onCollisionEnter', this.onCollisionEnter, this);
    this.maskBallNode = this.node.getParent().getParent().getChildByName('WhiteMaskBall').getChildByName('MoveMaskBall');
    this.maskBallNode.active = false;

    EventManager.Instance().AddEventListener('WhiteBallHitted', this, this.whiteBallHitted);
    // 重新设置白球的位置
    EventManager.Instance().AddEventListener('ResetMainBallPosition', this, this.resetMainBallPosition);
    EventManager.Instance().AddEventListener('WebResetMainBallPosition', this, this.webResetMainBallPosition);

    // 重新放置白球的位置
    EventManager.Instance().AddEventListener('InitMainBallPosition', this, this.initMainBallPosition);
    EventManager.Instance().AddEventListener('StartGame', this, this.startGame);

    EventManager.Instance().AddEventListener('AwakeMainWhiteBall', this, this.awakeMainWhiteBall);
    EventManager.Instance().AddEventListener('SleepMainWhiteBall', this, this.sleepMainWhiteBall);
  }
  start() {
  }
  public startGame() {
    this.maskBallNode.active = false;
  }
  update(deltaTime: number) {
    if (!BilliardGame.Instance().actionStage.isInitStage()) {
      this.maskBallNode.active = false;
    }
    this.correctVelocityLine(deltaTime);
    // 检测并处理白球与目标球首次碰撞逻辑
    this.targetBallHitCollision();
    // 球基类，会判断是否进球，以及边界处理
    super.update(deltaTime);
    this.prePosition.set(this.ballNode.worldPosition);
  }
  
  // 检测首次碰撞
  public targetBallHitCollision() {
    if (BilliardGame.Instance().hasCollisioned) return;
    if (!BilliardGame.Instance().actionStage.isForceFinished()) return;
    const aimBall = BilliardGame.Instance().getCurrentHitBall();
    if (!aimBall) return;
    // 前置碰撞 和 后置碰撞处理
    if (this.isNearCollision() || this.isMissedCollision()) {
      BilliardGame.Instance().hasCollisioned = true;
      this.ballBody.getLinearVelocity(this.initSpinVelocity);
      this.isNeedCorrect = false;
      this.virtualBallPosition.set(BilliardGame.Instance().getVirtualHitBall().worldPosition);
      
      this.setFirstCollideBall(BallAttribute.getBallByName(aimBall.name));
      this.hitBallPosition.set(aimBall.worldPosition);
      // 白球的速度分解到白球和目标球
      this.velocityDecomposition(aimBall);
      console.log('xxxxxx is algorith decom collision !!!!!!', BilliardGame.Instance().hasCollisioned, aimBall.name);

      this.ballBody.getLinearVelocity(this.currentLinearVelocity);
      const v = this.currentLinearVelocity.x * this.currentLinearVelocity.x + 
        this.currentLinearVelocity.z * this.currentLinearVelocity.z;
      const volume = Math.min(Math.max(v / 6, 1), 10) / 10;
      SoundManager.Instance().playBallsCollide(volume);
    }
  }
  public onCollisionEnter(event: ICollisionEvent) {
    if (!BilliardGame.Instance().actionStage.isForceFinished()) {
      return;
    }
    if (this.isHittedBallBame(event.otherCollider.node.name)) {
      if (!this.hasInHole && this.ballNode.active) {
        if (!BilliardGame.Instance().hasCollisioned) {
          BilliardGame.Instance().hasCollisioned = true;
          this.ballBody.getLinearVelocity(this.initSpinVelocity);
          this.isNeedCorrect = false;
          this.setFirstCollideBall(BallAttribute.getBallByName(event.otherCollider.node.name));
          console.log('xxxxxx is system collision !!!!!! ', event.otherCollider.node.name);
        }
      }
    }
    if (this.isDeskEdge(event.otherCollider.node.name)) {
      this.isNeedCorrect = false;
    }
    if (this.isHoleName(event.otherCollider.node.name)) {
      this.isNeedCorrect = false;
    }
    super.onCollisionEnter(event);
  }
  public whiteBallHitted(name: string, param: any) {
    // 先对齐白球的位置
    this.node.setWorldPosition(param.pos);
    // spin.x 和 spin.y 表示的是这个方向上沿半径的比例
    BilliardGame.Instance().awakeAllBalls();
    const velocity = param.velocity;
    this.spinOffsetPos.set(param.spinOffsetPos);
    if (this.hasSpinBall(param.spinOffsetPos)) {
      // relativePoint 设置成null，就是在质心伤的冲量
      const relatePos = this.calcuHitSpherePosition(velocity);
      this.ballBody.applyImpulse(Vector3.mul(velocity, 5), relatePos);
    } else {
      this.ballBody.setLinearVelocity(Vector3.mul(velocity, 1.125));
    }

    this.ballBody.getLinearVelocity(this.initHitLineVelocity);
    this.initSpinVelocity.set(this.initHitLineVelocity);
    this.initHitLineVelocity = this.initHitLineVelocity.normalize();
    this.initWhiteBallPosition.set(this.node.worldPosition);
    
    // 发出白球后，需要修正，产生碰撞之后，修正关闭
    this.isNeedCorrect = true;
    // 以击打白球为第1帧
    BilliardGame.Instance().initSyncFrame();
    WebFrameController.Instance().startStepFrame();
  }
  public resetMainBallPosition(name: string, pos: Vec3) {
    this.setMainBallPosition(pos);
    new SendWebSocket().sendMainBallPosition(0, pos);
  }

  public webResetMainBallPosition(name: string, pos: Vec3) {
    this.setMainBallPosition(pos);
  }

  public initMainBallPosition(name: string, pos: Vec3) {
    this.ballNode.setPosition(pos);
    this.outHole();
  }
  public awakeMainWhiteBall(name: string, data: any) {
    const mainBody = this.node.getComponent(RigidBody);
    mainBody.wakeUp();
    this.resetAngularDamping();
    mainBody.type = ERigidBodyType.DYNAMIC;
  }
  public sleepMainWhiteBall(name: string, data: any) {
    const mainBody = this.node.getComponent(RigidBody);
    mainBody.setAngularVelocity(Vec3.ZERO);
    mainBody.clearState();
    this.setAngularDamping(1.0);
    mainBody.type = ERigidBodyType.STATIC;
  }
  public clearState() {
    const mainBody = this.node.getComponent(RigidBody);
    mainBody.setAngularVelocity(Vec3.ZERO);
    mainBody.clearState();
    mainBody.clearForces();
    mainBody.clearVelocity();
    mainBody.sleep();
  }
  private setFirstCollideBall(ball: BallAttribute) {
    if (!BilliardGame.Instance().firstCollideBall) {
      BilliardGame.Instance().firstCollideBall = ball;
      console.log('first collision ball xxx ', ball.ballName());
    }
  }

  private isMissedCollision() {
    const targetBall = BilliardGame.Instance().getCurrentHitBall();
    if (!targetBall) return false;
    const virtualHitBall = BilliardGame.Instance().getVirtualHitBall();
    if (!virtualHitBall) return false;
    // 目标球
    if (GeometryTool.isBetweenCollision(this.prePosition, this.ballNode.worldPosition, virtualHitBall.worldPosition, targetBall.worldPosition)) {
      return true;
    }
    return false;
  }

  private isNearCollision() {
    const dt = 0.0265; // 比边界检测大一点，这样出界就一定能检测到碰撞
    const targetBall = BilliardGame.Instance().getCurrentHitBall();
    const virtualHitBall = BilliardGame.Instance().getVirtualHitBall();
    if (!targetBall) return false;
    if (!virtualHitBall) return false;
    this.ballBody.getLinearVelocity(this.whiteLineVelocity);
    const nextPosition = new Vec3(
      this.ballNode.worldPosition.x + dt * this.whiteLineVelocity.x,
      this.ballNode.worldPosition.y,
      this.ballNode.worldPosition.z + dt * this.whiteLineVelocity.z,
    );
    // 目标球
    if (GeometryTool.isBetweenCollision(this.ballNode.worldPosition, nextPosition, virtualHitBall.worldPosition, targetBall.worldPosition)) {
      return true;
    }
    return false;
  }

  private velocityDecomposition(ball: Node) {
    const dt = 0.0125;
    const w = new Vec3(BilliardGame.Instance().getReflectVector());
    const t = new Vec3(BilliardGame.Instance().getRefractVector());
    const v = new Vec3();
    this.ballBody.getLinearVelocity(v);
    const ww = (v.x * t.z - v.z * t.x) / (w.x * t.z - w.z * t.x);
    const tw = (v.x * w.z - v.z * w.x) / (t.x * w.z - w.x * t.z);
    // 设置白球的速度
    w.x = w.x * ww; w.y = 0; w.z = w.z * ww;
    const spinw = this.calcuSpinVelocity(w); // 加塞改变的速度
    this.ballBody.setLinearVelocity(spinw);
    const wpos = new Vec3(this.virtualBallPosition.x + dt * w.x,
      this.ballNode.worldPosition.y,
      this.virtualBallPosition.z + dt * w.z,
    );
    // 设置白球的位置
    this.ballNode.setWorldPosition(wpos);
    // 设置目标球的速度
    t.x = t.x * tw; t.y = 0; t.z = t.z * tw;
    const tbody = ball.getComponent(RigidBody);
    tbody.setLinearVelocity(t);
    // 设置位置
    const hpos =  new Vec3(this.hitBallPosition.x + dt * t.x,
      ball.worldPosition.y,
      this.hitBallPosition.z + dt * t.z,
    );

    ball.setWorldPosition(hpos);
  }
  private correctVelocityLine(dt: number) {
    if (BilliardGame.Instance().hasCollisioned) {
      return;
    }
    if (!BilliardGame.Instance().actionStage.isForceFinished()) {
      return;
    }
    const virtualBall = BilliardGame.Instance().getVirtualHitBall();
    if (!virtualBall) { 
      return ;
    }
    if (!BilliardGame.Instance().aimType.isAimTargetBall()) {
      return;
    }
    if (!this.isNeedCorrect) {
      return;
    }
    this.ballBody.getLinearVelocity(this.whiteLineVelocity);
    const dist = 4;
    // 在中间部分进行纠正
    if (this.node.worldPosition.x < TableAtrribute.DeskHalfAxisXLength - dist &&
      this.node.worldPosition.x > -TableAtrribute.DeskHalfAxisXLength + dist &&
      this.node.worldPosition.z < TableAtrribute.DeskHalfAxisZLength - dist &&
      this.node.worldPosition.z > -TableAtrribute.DeskHalfAxisZLength + dist
    ) {
      this.ballBody.getLinearVelocity(this.whiteLineVelocity);
      this.whiteLineVelocity = this.initHitLineVelocity.multiplyScalar(this.whiteLineVelocity.length());
      
      this.prePosition = GeometryTool.projectSegmentPoint(
        this.initWhiteBallPosition,
        virtualBall.worldPosition,
        this.ballNode.worldPosition,
        this.prePosition,
      );
      this.ballNode.setWorldPosition(this.prePosition);
      
    }
  }
  // 重新设置白球的位置，需要考虑是否有其他球
  private setMainBallPosition(pos: Vec3) {
    if (Math.abs(pos.x) > TableAtrribute.DeskHalfAxisXLength - 0.3) {
      return;
    }
    if (Math.abs(pos.z) > TableAtrribute.DeskHalfAxisZLength - 0.3) {
      return;
    }
    const balls = BilliardGame.Instance().getActiveBalls();
    // 与其他球重叠, 则不能放置
    for (const ball of balls) {
      const dist = (ball.worldPosition.x - pos.x) * (ball.worldPosition.x - pos.x) +
                   (ball.worldPosition.z - pos.z) * (ball.worldPosition.z - pos.z);
      if (dist < this.diameter + 0.000000001) {
        return;
      }
    }

    if (BilliardGame.Instance().isFreeKickBall()) {
      this.maskBallNode.active = true;
      this.maskBallNode.setWorldPosition(pos);
    }

    this.ballNode.setWorldPosition(pos);
    this.outHole();
  }
  // 计算加塞带来的速度变化
  private calcuSpinVelocity(velo: Vec3, ) {
    if (!BilliardGame.Instance().hasSpinHitBall) {
      return velo;
    }
    const newVelo = new Vec3(velo);
    const duVelo = new Vec3(this.initSpinVelocity);
    // 上下塞
    // y > 0  加一个前进方向的速度，但是不要比目标球更快
    var scaleY = this.spinOffsetPos.y;
    if (this.spinOffsetPos.y > 0) {
      scaleY = scaleY * 0.2;
    }
    // y < 0  加一个后退后退的速度
    console.log('spin y ', this.spinOffsetPos.x, this.spinOffsetPos.y);
    
    duVelo.multiplyScalar(scaleY);

    newVelo.add(duVelo);

    // 右赛 > 0 方向，左右赛增加幅度不能太多
    const lrVelo = new Vec3(this.initSpinVelocity.z, 0, -this.initSpinVelocity.x);
    lrVelo.multiplyScalar(this.spinOffsetPos.x * 0.1);
    newVelo.add(lrVelo);

    return newVelo;
    // 左右塞
  }
  private hasSpinBall(offset: Vec3) {
    return offset.x > 0.1 || offset.y > 0.1 || offset.z > 0.1;
  }
  // 计算击球的点，这里没有算加塞偏移（因为碰撞本身也不准确）
  private calcuHitSpherePosition(velocity: Vec3) {
    const relateForce = new Vec3(-this.radius * velocity.x, 0, -this.radius * velocity.z);
    return relateForce;
  }
}
